// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

//! Test the methods generated by the Builder derive macro.
//! Tests directly on the actual Xcm struct and Instruction enum.

use frame_support::BoundedVec;
use xcm::latest::prelude::*;

#[test]
fn builder_pattern_works() {
	let asset: Asset = (Here, 100u128).into();
	let beneficiary: Location = [0u8; 32].into();
	let message: Xcm<()> = Xcm::builder()
		.receive_teleported_asset(asset.clone())
		.buy_execution(asset.clone(), Unlimited)
		.deposit_asset(asset.clone(), beneficiary.clone())
		.build();
	assert_eq!(
		message,
		Xcm(vec![
			ReceiveTeleportedAsset(asset.clone().into()),
			BuyExecution { fees: asset.clone(), weight_limit: Unlimited },
			DepositAsset { assets: asset.into(), beneficiary },
		])
	);
}

#[test]
fn default_builder_requires_buy_execution() {
	let asset: Asset = (Here, 100u128).into();
	let beneficiary: Location = AccountId32 { id: [0u8; 32], network: None }.into();
	// This is invalid, since it doesn't pay for fees.
	// This is enforced by the runtime, because the build() method doesn't exist
	// on the resulting type.
	// let message: Xcm<()> = Xcm::builder()
	//     .withdraw_asset(asset.clone().into())
	//     .deposit_asset(asset.into(), beneficiary)
	//     .build();

	// To be able to do that, we need to use the explicitly unpaid variant
	let message: Xcm<()> = Xcm::builder_unpaid()
		.unpaid_execution(Unlimited, None)
		.withdraw_asset(asset.clone())
		.deposit_asset(asset.clone(), beneficiary.clone())
		.build(); // This works
	assert_eq!(
		message,
		Xcm(vec![
			UnpaidExecution { weight_limit: Unlimited, check_origin: None },
			WithdrawAsset(asset.clone().into()),
			DepositAsset { assets: asset.clone().into(), beneficiary: beneficiary.clone() },
		])
	);

	// The other option doesn't have any limits whatsoever, so it should
	// only be used when you really know what you're doing.
	let message: Xcm<()> = Xcm::builder_unsafe()
		.withdraw_asset(asset.clone())
		.deposit_asset(asset.clone(), beneficiary.clone())
		.build();
	assert_eq!(
		message,
		Xcm(vec![
			WithdrawAsset(asset.clone().into()),
			DepositAsset { assets: asset.clone().into(), beneficiary },
		])
	);
}

#[test]
fn default_builder_allows_clear_origin_before_buy_execution() {
	let asset: Asset = (Here, 100u128).into();
	let beneficiary: Location = [0u8; 32].into();
	let message: Xcm<()> = Xcm::builder()
		.receive_teleported_asset(asset.clone())
		.clear_origin()
		.buy_execution(asset.clone(), Unlimited)
		.deposit_asset(asset.clone(), beneficiary.clone())
		.build();
	assert_eq!(
		message,
		Xcm(vec![
			ReceiveTeleportedAsset(asset.clone().into()),
			ClearOrigin,
			BuyExecution { fees: asset.clone(), weight_limit: Unlimited },
			DepositAsset { assets: asset.into(), beneficiary },
		])
	);
}

#[test]
fn bounded_vecs_use_vecs_and_truncate_them() {
	let claimer = Location::parent();
	// We can use a vec instead of a bounded vec for specifying hints.
	let xcm: Xcm<()> = Xcm::builder_unsafe()
		.set_hints(vec![AssetClaimer { location: claimer.clone() }])
		.build();
	assert_eq!(
		xcm,
		Xcm(vec![SetHints {
			hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer {
				location: Location::parent()
			},]),
		},])
	);

	// If we include more than the limit they'll get truncated.
	let xcm: Xcm<()> = Xcm::builder_unsafe()
		.set_hints(vec![
			AssetClaimer { location: claimer.clone() },
			AssetClaimer { location: Location::here() },
		])
		.build();
	assert_eq!(
		xcm,
		Xcm(vec![SetHints {
			hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer {
				location: Location::parent()
			},]),
		},])
	);

	let xcm: Xcm<()> = Xcm::builder()
		.withdraw_asset((Here, 100u128))
		.set_hints(vec![AssetClaimer { location: claimer }])
		.clear_origin()
		.pay_fees((Here, 10u128))
		.deposit_asset(All, [0u8; 32])
		.build();
	assert_eq!(
		xcm,
		Xcm(vec![
			WithdrawAsset(Asset { id: AssetId(Location::here()), fun: Fungible(100) }.into()),
			SetHints {
				hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer {
					location: Location::parent()
				}])
			},
			ClearOrigin,
			PayFees { asset: Asset { id: AssetId(Location::here()), fun: Fungible(10) } },
			DepositAsset {
				assets: All.into(),
				beneficiary: AccountId32 { id: [0u8; 32], network: None }.into()
			},
		])
	);
}
